﻿using System.Text;

using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;

namespace Tszy.Unlimited.Base.Common;

/// <summary>
/// AES 算法工具类。
/// </summary>
public static class AESUtility
{
    private const int AES_BLOCK_SIZE = 128;
    private const string AES_GCM_CIPHER_ALG = "AES/GCM/NoPadding";

    /// <summary>
    /// 基于 GCM 模式解密数据。
    /// </summary>
    /// <param name="keyBytes">AES 密钥字节数组。</param>
    /// <param name="ivBytes">加密使用的初始化向量字节数组。</param>
    /// <param name="aadBytes">加密使用的附加数据包字节数组。</param>
    /// <param name="cipherBytes">待解密数据字节数组。</param>
    /// <returns>解密后的数据字节数组。</returns>
    public static byte[] DecryptWithGCM(byte[] keyBytes, byte[] ivBytes, byte[] aadBytes, byte[] cipherBytes)
    {
        if (keyBytes == null) throw new ArgumentNullException(nameof(keyBytes));
        if (ivBytes == null) throw new ArgumentNullException(nameof(ivBytes));
        if (aadBytes == null) throw new ArgumentNullException(nameof(aadBytes));
        if (cipherBytes == null) throw new ArgumentNullException(nameof(cipherBytes));

        IBufferedCipher cipher = CipherUtilities.GetCipher(AES_GCM_CIPHER_ALG);
        ICipherParameters aeadParams = new AeadParameters(
            new KeyParameter(keyBytes),
            AES_BLOCK_SIZE,
            ivBytes,
            aadBytes
        );
        cipher.Init(false, aeadParams);
        byte[] plainBytes = new byte[cipher.GetOutputSize(cipherBytes.Length)];
        int len = cipher.ProcessBytes(cipherBytes, 0, cipherBytes.Length, plainBytes, 0);
        cipher.DoFinal(plainBytes, len);
        return plainBytes;
    }

    /// <summary>
    /// 基于 GCM 模式解密数据。
    /// </summary>
    /// <param name="key">AES 密钥。</param>
    /// <param name="iv">加密使用的初始化向量。</param>
    /// <param name="aad">加密使用的附加数据包。</param>
    /// <param name="cipherText">经 Base64 编码后的待解密数据。</param>
    /// <returns>解密后的文本数据。</returns>
    public static string DecryptWithGCM(string key, string iv, string aad, string cipherText)
    {
        if (key == null) throw new ArgumentNullException(nameof(key));
        if (iv == null) throw new ArgumentNullException(nameof(iv));
        if (cipherText == null) throw new ArgumentNullException(nameof(cipherText));

        byte[] plainBytes = DecryptWithGCM(
            keyBytes: Encoding.UTF8.GetBytes(key),
            ivBytes: Encoding.UTF8.GetBytes(iv),
            aadBytes: Encoding.UTF8.GetBytes(aad ?? string.Empty),
            cipherBytes: Convert.FromBase64String(cipherText)
        );
        return Encoding.UTF8.GetString(plainBytes);
    }
}